﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace pbvs.Calculations
{
    public class LastSurvivorAnnuity
    {
        private readonly IList<decimal> _ax;
        private readonly IList<decimal> _ay;
        private readonly IList<decimal> _lx;
        private readonly IList<decimal> _ly;
        private readonly IList<decimal> _lxPo;
        private readonly IList<decimal> _lyPo;
        private readonly IList<decimal> _lyPt;
        private readonly IList<decimal> _lxy;
        private readonly IList<decimal> _lxPoY;
        private readonly IList<decimal> _lxLyPo;
        private readonly IList<decimal> _lxLyPt;
        private readonly IList<decimal> _dxy;
        private readonly IList<decimal> _dxPoY;
        private readonly IList<decimal> _dxyPo;
        private readonly IList<decimal> _dxyPt;
        private readonly IList<decimal> _nxy;
        private readonly IList<decimal> _nxPoY;
        private readonly IList<decimal> _nxyPo;
        private readonly IList<decimal> _nxyPt;
        private readonly IList<decimal> _axy;
        private readonly IList<decimal> _axPoY;
        private readonly IList<decimal> _axyPo;
        private readonly IList<decimal> _axyPt;
        private readonly IList<int> _jointSurvivor1;
        private readonly IList<int> _jointSurvivor2;
        private readonly CommutationTable.Gender _gender;
        private readonly CommutationTable.Gender _survivorGender;
        private readonly decimal _interestRate;
        private readonly decimal _cola;
        private readonly decimal _retirementAge;
        private readonly decimal _beneficiaryAge;
	    private readonly IList<CommutationTable> _ctVars;
        public decimal AttainedAge { get; set; }
        public decimal SurvivorAge { get; set; }
        public decimal SurvivorPercentage { get; set; }
        public decimal BeneficiaryNormalRa { get; set; }
        public int FirstNYears { get; set; }
        public bool EqualAges { get; set; }

        public decimal Lsa
        {
            get { return this._ax[0] + this.SurvivorPercentage*(this._ay[0] - this._axy[0]); }
        }

        public decimal Lsa2
        {
            get { return this._ax[1] + this.SurvivorPercentage*(this._ay[0] - this._axPoY[0]); }
        }

        public decimal Lsa3
        {
            get { return this._ax[0] + this.SurvivorPercentage*(this._ay[1] - this._axyPo[0]); }
        }

        public decimal Lsa4
        {
            get { return this._ax[1] + this.SurvivorPercentage*(this._ay[1] - this._axy[1]); }
        }

        public decimal AgeWeightNormal 
        {
            get
            {
                return (1 - (Valuation.FractionOfYear(this._retirementAge)))*
                       (1 - (Valuation.FractionOfYear(this.BeneficiaryNormalRa)));
            }
        }

        public decimal AgeWeightNormal2
        {
            get
            {
                return Valuation.FractionOfYear(this._retirementAge)*
                       (1 - (Valuation.FractionOfYear(this.BeneficiaryNormalRa)));
            }
        }

        public decimal AgeWeightNormal3
        {
            get
            {
                return (1 - (Valuation.FractionOfYear(this._retirementAge)))*
                       Valuation.FractionOfYear(this.BeneficiaryNormalRa);
            }
        }

        public decimal AgeWeightNormal4
        {
            get
            {
                return Valuation.FractionOfYear(this._retirementAge)*
                       Valuation.FractionOfYear(this.BeneficiaryNormalRa);
            }
        }

        public decimal LsaNormalRaFactor
        {
            get { return Lsa*AgeWeightNormal + Lsa2*AgeWeightNormal2 + Lsa3*AgeWeightNormal3 + Lsa4*AgeWeightNormal4; }
        }

        public int FnyIndex
        {
            get { return _jointSurvivor1.IndexOf(AgeInYears(this._retirementAge) + this.FirstNYears); }
        }

        public int FnyIndex2
        {
            get { 
                int fnyIndex2 = AgeInYears(this.AttainedAge) + this.FirstNYears;
                return (AgeInYears(this.AttainedAge) - AgeInYears(this._beneficiaryAge)) ==
                        ((AgeInYears(this.AttainedAge) + this.FirstNYears) -
                         (AgeInYears(this._beneficiaryAge) + this.FirstNYears))
                            ? this._jointSurvivor1.IndexOf(fnyIndex2)
                            : this._jointSurvivor1.IndexOf(fnyIndex2 + 1);
            }
        }

        public decimal LsaPlusFny
        {
            get { return EqualAges ? this._axy[this.FnyIndex] : this._axyPo[this.FnyIndex]; }
        }

        public decimal LsaPlusFny2
        {
            get { return EqualAges ? this._axPoY[this.FnyIndex2] : this._axy[this.FnyIndex2]; }
        }

        public decimal LsaPlusFny3
        {
            get
            {
                return EqualAges
                           ? this._axyPo[_jointSurvivor1.IndexOf(AgeInYears(this.AttainedAge) + this.FirstNYears)]
                           : this._axyPt[_jointSurvivor1.IndexOf(AgeInYears(this.AttainedAge) + this.FirstNYears)];
            }
        }

        public decimal LsaPlusFny4
        {
            get
            {
                return EqualAges
                           ? this._axy[_jointSurvivor1.IndexOf(AgeInYears(this.AttainedAge) + this.FirstNYears) + 1]
                           : this._axyPo[_jointSurvivor1.IndexOf(AgeInYears(this.AttainedAge) + this.FirstNYears) + 1];
            }
        }

        public decimal AgeWeightPlusFny
        {
            get
            {
                return (1 - Valuation.FractionOfYear(this.AttainedAge + this.FirstNYears))*
                       (1 - Valuation.FractionOfYear(this.SurvivorAge + this.FirstNYears));
            }
        }

        public decimal AgeWeightPlusFny2
        {
            get
            {
                return Valuation.FractionOfYear(this.AttainedAge + this.FirstNYears)*
                       (1 - Valuation.FractionOfYear(this.SurvivorAge + this.FirstNYears));
            }
        }

        public decimal AgeWeightPlusFny3
        {
            get
            {
                return (1 - Valuation.FractionOfYear(this.AttainedAge + this.FirstNYears))*
                       Valuation.FractionOfYear(this.SurvivorAge + this.FirstNYears);
            }
        }

        public decimal AgeWeightPlusFny4
        {
            get
            {
                return Valuation.FractionOfYear(this.AttainedAge + this.FirstNYears)*
                       Valuation.FractionOfYear(this.SurvivorAge + this.FirstNYears);
            }
        }

        public decimal LsaPlusFnyFactor
        {
            get
            {
                return LsaPlusFny*AgeWeightPlusFny + LsaPlusFny2*AgeWeightPlusFny2 + LsaPlusFny3*AgeWeightPlusFny3 +
                       LsaPlusFny4*AgeWeightPlusFny4;
            }
        }

        public LastSurvivorAnnuity(List<int> js1, List<int> js2, CommutationTable.Gender gender, CommutationTable.Gender survivorGender, decimal interestRate, decimal cola, decimal retirementAge, decimal beneficiaryAge, bool useFirst20, IList<CommutationTable> ctVars)
        {
            this._jointSurvivor1 = js1;
            this._jointSurvivor2 = js2;
            this._gender = gender;
            this._survivorGender = survivorGender;
            this._interestRate = interestRate;
            this._cola = cola;
            this._retirementAge = retirementAge;
            this._beneficiaryAge = beneficiaryAge;
			this._ctVars = ctVars;
			this._ax = GetVariableList(_jointSurvivor1, this._gender, true, useFirst20);
            this._ay = GetVariableList(_jointSurvivor2, this._survivorGender, true, useFirst20);
            this._lx = GetVariableList(_jointSurvivor1, this._gender, false, useFirst20);
            this._ly = GetVariableList(_jointSurvivor2, this._survivorGender, false, useFirst20);
            this._lxPo = GetOffsetVariableList(this._lx, 1);
            this._lyPo = GetOffsetVariableList(this._ly, 1);
            this._lyPt = GetOffsetVariableList(this._ly, 2);
            this._lxy = this._ly.Select(this._ly.IndexOf).Select(index => (this._lx[index] * this._ly[index]) / 1000000).ToList();
            this._lxPoY = this._lxPo.Select(this._lxPo.IndexOf).Select(index => (this._lxPo[index] * this._ly[index]) / 1000000).ToList();
            this._lxLyPo = this._lx.Select(this._lx.IndexOf).Select(index => (this._lx[index] * this._lyPo[index]) / 1000000).ToList();
            this._lxLyPt = this._lx.Select(this._lx.IndexOf).Select(index => (this._lx[index] * this._lyPt[index]) / 1000000).ToList();
            this._dxy = GetDxVariableList(this._lxy, this._interestRate, 0);
            this._dxPoY = GetDxVariableList(this._lxPoY, this._interestRate, 1);
            this._dxyPo = GetDxVariableList(this._lxLyPo, this._interestRate, 1);
            this._dxyPt = GetDxVariableList(this._lxLyPt, this._interestRate, 2);
            this._nxy = GetSummation(this._dxy.ToList());
            this._nxPoY = GetSummation(this._dxPoY.ToList());
            this._nxyPo = GetSummation(this._dxyPo.ToList());
            this._nxyPt = GetSummation(this._dxyPt.ToList());
            this._axy = GetAxyVariableList(this._nxy, this._dxy);
            this._axPoY = GetAxyVariableList(this._nxPoY, this._dxPoY);
            this._axyPo = GetAxyVariableList(this._nxyPo, this._dxyPo);
            this._axyPt = GetAxyVariableList(this._nxyPt, this._dxyPt);
        }

        public decimal CalculateLsan(decimal ageNDivideRetirementAx, decimal survivorDivideBeneficiaryAx,
                             decimal ageNDivideRetirement, decimal survivorDivideBeneficiary, decimal onePlusI20,
                             decimal colaCalc)
        {
            return LsaNormalRaFactor -
                   (ageNDivideRetirementAx +
                    this.SurvivorPercentage*
                    (survivorDivideBeneficiaryAx -
                     LsaPlusFnyFactor*ageNDivideRetirement*survivorDivideBeneficiary*onePlusI20))*colaCalc;
        }

        public decimal CalculateNLsa(decimal ageNAxDivideRetirement, decimal survivorAxDivideBeneficiary,
                                     decimal ageNDivideRetirement, decimal survivorDivideBeneficiary)
        {
            return ageNAxDivideRetirement +
                   this.SurvivorPercentage*
                   (survivorAxDivideBeneficiary -
                    LsaPlusFnyFactor*ageNDivideRetirement*survivorDivideBeneficiary*
                    Convert.ToDecimal(Math.Pow(1 + Convert.ToDouble(this._interestRate),
                                               Convert.ToDouble(this.FirstNYears - this._retirementAge +
                                                                this.AttainedAge))));
        }

        public decimal CalculateCase2Factor(decimal longTermDeferredEndowment, decimal longTermDeferredEndowment2)
        {
            return longTermDeferredEndowment*longTermDeferredEndowment2*this.LsaNormalRaFactor;
        }

        private IList<decimal> GetVariableList(IEnumerable<int> survivorList, CommutationTable.Gender gender, bool useAx, bool useFirst20)
        {
            List<decimal> list = new List<decimal>();
            foreach (int age in survivorList)
            {
                if (age <= 120)
                {
                    CommutationTable ct = new CommutationTable(age, gender, this._ctVars, this._cola);
                    if (gender == CommutationTable.Gender.Male)
                    {
                        list.Add(useAx ? ct.CalculateAxVariables(useFirst20) : ct.Male2022);
                    }
                    else
                    {
                        list.Add(useAx ? ct.CalculateAxVariables(useFirst20) : ct.Female2022);
                    }
                }
                else
                {
                    list.Add(0);
                }
            }
            return list;
        }

        private IList<decimal> GetOffsetVariableList(IList<decimal> origList, int offsetFactor)
        {
            IList<decimal> offsetList = origList.Where(var => origList.IndexOf(var) >= offsetFactor).ToList();
            for (int x = 0; x < offsetFactor; x++)
            {
                offsetList.Add(0);
            }
            return offsetList;
        }

        private IList<decimal> GetDxVariableList(IEnumerable<decimal> varList, decimal interestRate, int offsetFactor)
        {
            IList<decimal> dxy = new List<decimal>();
            int[] count = { 0 };
            foreach (decimal value in varList.Select(var => var *
                                                            Convert.ToDecimal(
                                                                Math.Pow(Convert.ToDouble((1 + this._cola) / (1 + interestRate)),
                                                                         Convert.ToDouble(
                                                                             Convert.ToDecimal((AgeInYears(this._retirementAge) + count[0]) +
                                                                                               (AgeInYears(this._beneficiaryAge) + count[0]) + offsetFactor) / 2)))))
            {
                dxy.Add(Convert.ToDecimal(value));
                count[0]++;
            }
            return dxy;
        }

        private IList<decimal> GetSummation(IList<decimal> varList)
        {
            IList<decimal> summation = new List<decimal>();
            foreach (decimal var in varList.ToList())
            {
                summation.Add(varList.Sum());
                varList.Remove(var);
            }
            return summation;
        }

        private IList<decimal> GetAxyVariableList(IList<decimal> varList1, IList<decimal> varList2)
        {
            IList<decimal> axy = new List<decimal>();
            foreach (decimal var in varList1)
            {
                int index = varList1.IndexOf(var);
                decimal value = 0;
                if (var > 0 && varList2[index] > 0)
                {
                    value = ((var / varList2[index]) - (11m / 2m / 12m)) * Convert.ToDecimal((Math.Pow(Convert.ToDouble((1 + this._cola)), Convert.ToDouble(-(11m) / 2m / 12m))));
                }
                axy.Add(value);
            }
            return axy;
        }

        private int AgeInYears(decimal age)
        {
            return Convert.ToInt32(Math.Floor(age));
        }
    }
}